﻿using PMS.Core.Caching;
using PMS.Core.Configuration.CommonEnumConfigs;
using PMS.Core.Utils;
using PMS.Data.Entities.Customers;
using PMS.Data.Entities.Messages;
using PMS.Data.Entities.Settings;
using PMS.Services.Caching;
using PMS.Services.EntityServices.Customers;
using PMS.Services.EntityServices.Stores;
using PMS.Services.SharedKernel;
using PMS.Services.Utils.Messages;
using System;
using System.Collections.Generic;
using System.Linq;

namespace PMS.Services.EntityServices.Messages
{
    /// <summary>
    /// Workflow message service
    ///  所有邮件、短信发送服务的调用组件
    ///  1： 邮件发送服务： 考虑邮件推送使用阿里云推送服务，阿里云推送对推送频率有时间间隔要求，虽然很短，但考虑触发直接发送时可能因为并发情况导致邮件发送失败风险
    ///                 故所有邮件发送采用队列邮件方式，并通过后台定时任务(TaskManager)统一发送邮件，控制邮件发送频率
    ///     发送邮件触发条件：
    ///     1: EmailAccountSettings.Enabled 为True
    ///     2: 发送邮件账号(EmailAccount)存在(使用阿里云邮件推送服务)且设置了相应的账号密码
    ///     3：发送邮件模板存在且IsActive为True
    ///     任一条件不满足则中止邮件推送
    /// </summary>
    public partial class WorkflowMessageService : IWorkflowMessageService
    {
        #region Fields
        private readonly EmailAccountSettings _emailAccountSettings;
        private readonly ICustomerService _customerService;
        private readonly IEmailAccountService _emailAccountService;
        private readonly IEmailMessageTemplateService _emailmessageTemplateService;
        private readonly IEmailMessageTemplateTokenProvider _emailmessagetemplateTokenProvider;
        private readonly IQueuedEmailService _queuedEmailService;
        private readonly IStoreContext _storeContext;
        private readonly IStoreService _storeService;
        private readonly EmailTokenizer _tokenizer;
        private readonly ICacheKeyService _cacheKeyService;
        private readonly IStaticCacheManager _staticCacheManager;
        private readonly ISMSSender _smsSender;
        private readonly ISMSAccountService _smsAccountService;
        private readonly ISMSTemplateService _smsTemplateService;

        #endregion

        #region Ctor
        public WorkflowMessageService(EmailAccountSettings emailAccountSettings,
            IStoreService storeService,
            IStoreContext storeContext,
            ICustomerService customerService,
            ICacheKeyService cacheKeyService,
            IStaticCacheManager cacheManager,
            ISMSSender smsSender,
            ISMSAccountService smsAccountService,
            ISMSTemplateService smsTemplateService,
            IQueuedEmailService queuedEmailService,
            IEmailAccountService emailAccountService,
            IEmailMessageTemplateService emailmessageTemplateService,
            IEmailMessageTemplateTokenProvider messagetemplateTokenProvider)
        {
            _tokenizer = new EmailTokenizer();
            _emailAccountSettings = emailAccountSettings;
            _customerService = customerService;
            _emailAccountService = emailAccountService;
            _emailmessageTemplateService = emailmessageTemplateService;
            _emailmessagetemplateTokenProvider = messagetemplateTokenProvider;
            _queuedEmailService = queuedEmailService;
            _storeContext = storeContext;
            _storeService = storeService;
            _cacheKeyService = cacheKeyService;
            _staticCacheManager = cacheManager;
            _smsSender = smsSender;
            _smsAccountService = smsAccountService;
            _smsTemplateService = smsTemplateService;
        }
        #endregion

        #region Utilities
        /// <summary>
        /// Get active message templates by the name
        /// </summary>
        /// <param name="messageTemplateName">Message template name</param>
        /// <param name="storeId">Store identifier</param>
        /// <returns>List of message templates</returns>
        protected virtual IList<EmailMessageTemplate> GetActiveEmailMessageTemplates(string messageTemplateName, int storeId)
        {
            //get message templates by the name
            var messageTemplates = _emailmessageTemplateService.GetMessageTemplatesByName(messageTemplateName, storeId);

            //no template found
            if (!messageTemplates?.Any() ?? true)
                return new List<EmailMessageTemplate>();

            //filter active templates
            messageTemplates = messageTemplates.Where(messageTemplate => messageTemplate.IsActive).ToList();

            return messageTemplates;
        }

        /// <summary>
        /// Get EmailAccount to use with a message templates
        /// </summary>
        /// <param name="messageTemplate">Message template</param>
        /// <param name="languageId">Language identifier</param>
        /// <returns>EmailAccount</returns>
        protected virtual EmailAccount GetEmailAccountOfMessageTemplate(EmailMessageTemplate messageTemplate)
        {
            var emailAccountId = messageTemplate.EmailAccountId;
            var emailAccount = _emailAccountService.GetEmailAccountById(emailAccountId);
            //?? _emailAccountService.GetAllEmailAccounts().FirstOrDefault();
            if (emailAccount == null) return null;
            //如果邮件模板的发送类型与发送账号的类型不一致，则无法调用阿里云邮件推送服务正确发送邮件
            return emailAccount.EmailSenderType != messageTemplate.EmailSenderType ? null : emailAccount;
        }

        protected virtual SMSAccount GetSMSAccountOfMessageTemplate(ShortMessageTemplate messageTemplate)
        {
            var smsAccountId = messageTemplate.SmsAccountId;
            var smsAccount = _smsAccountService.GetSMSAccountById(smsAccountId);
            if (smsAccount == null) return null;
            return smsAccount;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Sends a welcome message to a customer
        /// 新注册用户发送欢迎邮件 
        /// </summary>
        /// <param name="customer">Customer instance</param>
        /// <param name="customerPwd">CustomerPassword output to Email if needed</param>
        /// <returns>Queued email identifier</returns>
        public IList<int> SendCustomerRegisteredWelcomeEmail(Customer customer, string customerPwd = null)
        {
            if (customer == null)
                throw new ArgumentNullException(nameof(customer));

            if (!EmailSenderServiceEnable || string.IsNullOrEmpty(customer.Email)) return new List<int>();

            var store = _storeContext.CurrentStore;

            var messageTemplates = GetActiveEmailMessageTemplates(EmailMessageTemplateNames.CustomerWelcomeMessageAfterRegister, store.Id);
            if (!messageTemplates.Any())
                return new List<int>(); ;

            //tokens
            var commonTokens = new List<EmailMesssageTemplateToken>();
            _emailmessagetemplateTokenProvider.AddCustomerTokens(commonTokens, customer, customerPwd);

            return messageTemplates.Select(messageTemplate =>
            {
                //email account
                var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate);
                if (emailAccount != null)
                {
                    var tokens = new List<EmailMesssageTemplateToken>(commonTokens);
                    _emailmessagetemplateTokenProvider.AddStoreTokens(tokens, store, emailAccount);

                    var toEmail = customer.Email;
                    var toName = customer.NickName;

                    return SendNotification(messageTemplate, emailAccount, tokens, toEmail, toName);
                }
                return 0;
            }).Where(QueuedEmailId => QueuedEmailId > 0).ToList();
        }

        /// <summary>
        /// 当后台用户管理页修改用户密码后，发送用户账号密码更改邮件
        /// </summary>
        /// <param name="customer"></param>
        /// <param name="customerPwd"></param>
        /// <returns>Queued email identifier</returns>
        public IList<int> SendCustomerPasswordChangedEmail(Customer customer, string customerPwd = null)
        {
            if (customer == null)
                throw new ArgumentNullException(nameof(customer));

            if (!EmailSenderServiceEnable || string.IsNullOrEmpty(customer.Email)) return new List<int>();

            var store = _storeContext.CurrentStore;

            var messageTemplates = GetActiveEmailMessageTemplates(EmailMessageTemplateNames.CustomerPasswordChangedMessage, store.Id);
            if (!messageTemplates.Any())
                return new List<int>(); ;

            //tokens
            var commonTokens = new List<EmailMesssageTemplateToken>();
            _emailmessagetemplateTokenProvider.AddCustomerTokens(commonTokens, customer, customerPwd);

            return messageTemplates.Select(messageTemplate =>
            {
                //email account
                var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate);
                if (emailAccount != null)
                {
                    var tokens = new List<EmailMesssageTemplateToken>(commonTokens);
                    _emailmessagetemplateTokenProvider.AddStoreTokens(tokens, store, emailAccount);

                    var toEmail = customer.Email;
                    var toName = customer.NickName;

                    return SendNotification(messageTemplate, emailAccount, tokens, toEmail, toName);
                }
                return 0;
            }).Where(QueuedEmailId => QueuedEmailId > 0).ToList();
        }

        /// <summary>
        /// Sends an email validation message to a customer
        /// </summary>
        /// <param name="customer">Customer instance</param>
        /// <returns>Queued email identifier</returns>
        public virtual IList<int> SendCustomerEmailValidationEmail(Customer customer)
        {
            if (customer == null)
                throw new ArgumentNullException(nameof(customer));

            if (!EmailSenderServiceEnable || string.IsNullOrEmpty(customer.Email)) return new List<int>();
            var store = _storeContext.CurrentStore;

            var messageTemplates = GetActiveEmailMessageTemplates(EmailMessageTemplateNames.CustomerEmailValidationMessage, store.Id);
            if (!messageTemplates.Any())
                return new List<int>();

            //tokens
            var commonTokens = new List<EmailMesssageTemplateToken>();
            _emailmessagetemplateTokenProvider.AddCustomerTokens(commonTokens, customer);

            return messageTemplates.Select(messageTemplate =>
            {
                //email account
                var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate);

                var tokens = new List<EmailMesssageTemplateToken>(commonTokens);
                _emailmessagetemplateTokenProvider.AddStoreTokens(tokens, store, emailAccount);

                var toEmail = customer.Email;
                var toName = customer.NickName;

                return SendNotification(messageTemplate, emailAccount, tokens, toEmail, toName);
            }).ToList();
        }

        /// <summary>
        /// Sends password recovery message to a customer
        /// </summary>
        /// <param name="customer">Customer instance</param>
        /// <param name="languageId">Message language identifier</param>
        /// <returns>Queued email identifier</returns>
        public virtual IList<int> SendCustomerPasswordRecoveryEmail(Customer customer)
        {
            if (customer == null)
                throw new ArgumentNullException(nameof(customer));

            if (!EmailSenderServiceEnable || string.IsNullOrEmpty(customer.Email)) return new List<int>();
            var store = _storeContext.CurrentStore;
            var messageTemplates = GetActiveEmailMessageTemplates(EmailMessageTemplateNames.CustomerPasswordRecoveryMessage, store.Id);
            if (!messageTemplates.Any())
                return new List<int>();

            //tokens
            var commonTokens = new List<EmailMesssageTemplateToken>();
            _emailmessagetemplateTokenProvider.AddCustomerTokens(commonTokens, customer);

            return messageTemplates.Select(messageTemplate =>
            {
                //email account
                var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate);

                var tokens = new List<EmailMesssageTemplateToken>(commonTokens);
                _emailmessagetemplateTokenProvider.AddStoreTokens(tokens, store, emailAccount);

                var toEmail = customer.Email;
                var toName = customer.NickName;

                return SendNotification(messageTemplate, emailAccount, tokens, toEmail, toName);
            }).ToList();
        }

        /// <summary>
        /// Batch Sends Greeting message to a customer of Birthday
        /// </summary>
        /// <param name="customers">Customer instance List</param>
        /// <returns>Queued email identifier</returns>
        public virtual IList<int> SendBirthdayGreetingEmail(IList<Customer> customers)
        {
            if (customers == null)
                throw new ArgumentNullException(nameof(customers));
            if (customers.Count <= 0)
                return null;
            if (!EmailSenderServiceEnable) return new List<int>();

            var store = _storeContext.CurrentStore;
            var messageTemplates = GetActiveEmailMessageTemplates(EmailMessageTemplateNames.CustomerBirthdayGreetingMessage, store.Id);
            if (!messageTemplates.Any())
                return new List<int>();

            IList<int> ququeuedEmailId = new List<int>();
            foreach (var customer in customers)
            {
                if (string.IsNullOrEmpty(customer.Email))
                    continue;

                //tokens
                var commonTokens = new List<EmailMesssageTemplateToken>();
                _emailmessagetemplateTokenProvider.AddCustomerTokens(commonTokens, customer);

                ququeuedEmailId = ququeuedEmailId.Concat(messageTemplates.Select(messageTemplate =>
                {
                    //email account
                    var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate);

                    var tokens = new List<EmailMesssageTemplateToken>(commonTokens);
                    _emailmessagetemplateTokenProvider.AddStoreTokens(tokens, store, emailAccount);

                    var toEmail = customer.Email;
                    var toName = customer.NickName;

                    return SendNotification(messageTemplate, emailAccount, tokens, toEmail, toName);
                }).ToArray()).ToList();
            }
            return ququeuedEmailId;
        }

        /// <summary>
        /// Sends verifycode validation Email message to customer
        /// when customer changing EmailAccount
        /// </summary>
        /// <param name="customer"></param>
        /// <param name="email">email address to send verify code</param>
        /// <returns></returns>
        public virtual IList<int> SendCustomerChangeEmailAccountEmail(Customer customer, string email)
        {
            if (customer == null || string.IsNullOrEmpty(email))
                throw new ArgumentNullException(nameof(customer));
            
            if (!EmailSenderServiceEnable) return new List<int>();

            var store = _storeContext.CurrentStore;
            var messageTemplates = GetActiveEmailMessageTemplates(EmailMessageTemplateNames.CustomerSendChangeEmailAccountCodeMessage, store.Id);
            if (!messageTemplates.Any())
                return new List<int>();

            var verifyCodeKey = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.CustomerEmailVerifyCodeCacheKey, EmailMessageTemplateNames.CustomerSendChangeEmailAccountCodeMessage, customer.Id);
            var verifyCode = _staticCacheManager.Get(verifyCodeKey, () =>
            {//生成六位验证码
                return CommonHelper.GenerateRandomDigitCode(6);
            });
            //tokens
            var commonTokens = new List<EmailMesssageTemplateToken>();
            _emailmessagetemplateTokenProvider.AddCustomerTokens(commonTokens, customer);

            return messageTemplates.Select(messageTemplate =>
            {
                //email account
                var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate);

                var tokens = new List<EmailMesssageTemplateToken>(commonTokens);
                _emailmessagetemplateTokenProvider.AddStoreTokens(tokens, store, emailAccount);

                var toEmail = email;
                var toName = customer.NickName;

                return SendNotification(messageTemplate, emailAccount, tokens, toEmail, toName);
            }).ToList();
        }
        /// <summary>
        /// Send notification
        /// </summary>
        /// <param name="messageTemplate">EmailMessageTemplate</param>
        /// <param name="emailAccount">Email account</param>
        /// <param name="tokens">EmailMesssageTemplateToken</param>
        /// <param name="toEmailAddress">Recipient email address</param>
        /// <param name="toName">Recipient name</param>
        /// <param name="attachmentFilePath">Attachment file path</param>
        /// <param name="attachmentFileName">Attachment file name</param>
        /// <param name="replyToEmailAddress">"Reply to" email</param>
        /// <param name="replyToName">"Reply to" name</param>
        /// <param name="fromEmail">Sender email. If specified, then it overrides passed "emailAccount" details</param>
        /// <param name="fromName">Sender name. If specified, then it overrides passed "emailAccount" details</param>
        /// <param name="subject">Subject. If specified, then it overrides subject of a message template</param>
        /// <returns>Queued email identifier</returns>
        protected virtual int SendNotification(EmailMessageTemplate messageTemplate,
            EmailAccount emailAccount, IEnumerable<EmailMesssageTemplateToken> tokens,
            string toEmailAddress, string toName,
            string attachmentFilePath = null, string attachmentFileName = null,
            string replyToEmailAddress = null, string replyToName = null,
            string fromEmail = null, string fromName = null, string subject = null)
        {
            if (messageTemplate == null)
                throw new ArgumentNullException(nameof(messageTemplate));

            if (emailAccount == null)
                throw new ArgumentNullException(nameof(emailAccount));

            var bcc = string.IsNullOrEmpty(messageTemplate.BccEmailAddresses)
                    ? _emailAccountSettings.DefaultBccAddress + ":" + _emailAccountSettings.DefaultBccName + ";"
                    : messageTemplate.BccEmailAddresses;
            if (string.IsNullOrEmpty(subject))
                subject = messageTemplate.Subject;
            var body = messageTemplate.Body;

            //Replace subject and body tokens 
            var subjectReplaced = _tokenizer.Replace(subject, tokens, false);
            var bodyReplaced = _tokenizer.Replace(body, tokens, true);

            //limit name length
            toName = CommonHelper.EnsureMaximumLength(toName, 200);

            var email = new QueuedEmail
            {
                //根据模板邮件的发送类型决定邮件发送优先级：
                //触发类邮件(密码找回，注册验证，订单通知等) 高优先级
                //批量邮件(广告，订阅等)  低优先级
                Priority = messageTemplate.EmailSenderType == EmailSenderType.SingleMailSender ? QueuedEmailPriority.High : QueuedEmailPriority.Low,
                From = !string.IsNullOrEmpty(fromEmail) ? fromEmail : emailAccount.Email,
                FromName = !string.IsNullOrEmpty(fromName) ? fromName : emailAccount.DisplayName,
                To = toEmailAddress,
                ToName = toName,
                ReplyTo = !string.IsNullOrEmpty(replyToEmailAddress) ? replyToEmailAddress : emailAccount.ReplyTo,
                ReplyToName = replyToName,
                CC = string.Empty,
                Bcc = bcc,
                Subject = subjectReplaced,
                Body = bodyReplaced,
                AttachmentFilePath = attachmentFilePath,
                AttachmentFileName = attachmentFileName,
                IsActive = true,
                EmailAccountId = emailAccount.Id,
                DontSendBeforeDateUtc = messageTemplate.DelayPeriodMinutes <= 0 ? null
                    : (DateTime?)(DateTime.UtcNow + TimeSpan.FromMinutes(messageTemplate.DelayPeriodMinutes))
            };

            _queuedEmailService.InsertQueuedEmail(email);
            return email.Id;
        }
        #endregion

        #region Workflow for SMS
        /// <summary>
        /// Send VerifyCode Message to customer
        /// </summary>
        /// <param name="toCustomer"></param>
        /// <param name="templateName"></param>
        /// <returns></returns>
        public virtual bool SendVerifyMessageToCustomer(Customer toCustomer, string templateName)
        {
            if (toCustomer == null || string.IsNullOrEmpty(toCustomer.Mobile))
                return false;
            var store = _storeContext.CurrentStore;
            var smsTemplate = _smsTemplateService.GetMessageTemplateByName(templateName, store.Id);
            if (smsTemplate == null) return false;
            var smsAccount = GetSMSAccountOfMessageTemplate(smsTemplate);
            if (smsAccount == null || !smsAccount.IsActive) return false;

            var verifyCodeKey = _cacheKeyService.PrepareKey(CacheKeyConfigs.CustomerSMSVerifyCodeCacheKey, smsTemplate.SmsTemplateName, toCustomer.Id);
            var verifyCode = _staticCacheManager.Get(verifyCodeKey, () =>
            {//生成六位短信验证码
                return CommonHelper.GenerateRandomDigitCode(6);
            });
            try
            {
                //var templatePara = DeserializeStringToDictionary<string, string>("{code:" + verifyCode + "}");
                var templatePara = new Dictionary<string, string>
                {
                    { "code", verifyCode }
                };
                return _smsSender.SendSMS(smsAccount, toCustomer.Mobile, smsTemplate.TemplateId, templatePara);
            }
            catch (Exception)
            {
                return false;
            }
        }
        #endregion

        #region Properties
        public bool EmailSenderServiceEnable => _emailAccountSettings.Enabled;
        #endregion
    }
}
